Implement an abstract Fence type to expose waiting for command buffer completion.#1007
Conversation
|
@manon-traverse - is this |
|
#987 was merged, is it time to rebase this? |
@damyanp No it's mainly waiting for the metal-cpp update PR to get merged in: #1004 |
bcf1180 to
41f208f
Compare
damyanp
left a comment
There was a problem hiding this comment.
It's hard to convince myself this is the right abstraction since I don't think anything is actually using the abstraction - as far as I can tell, there isn't anything actually using the fence through the Fence interface?
The abstract is being used. However it is just being used for waiting for work completion by waiting on the signal value. Once @MarijnS95's work on command buffer submission on the queue is completed the Fence will be signaled by that function. Currently, the API-agnostic and API-specific code is fairly intertwined. As we get more of the graphics APIs covered in the API agnostic code this will become less of a problem. |
Move command buffer submission logic from each backend's Device into Queue::submit(), which takes ownership of the command buffers. For now it blocks internally until completion; a TODO marks that it will return a Fence once the Fence abstraction from PR llvm#1007 is available. - Metal: commit + waitUntilCompleted + error check - Vulkan: vkEndCommandBuffer + temporary fence + vkQueueSubmit + wait - DX12: CmdList Close + ExecuteCommandLists + fence signal/wait VulkanQueue now stores a VkDevice handle (with a TODO for lifetime management) so it can create/destroy fences independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move command buffer submission logic from each backend's Device into Queue::submit(), which takes ownership of the command buffers. For now it blocks internally until completion; a TODO marks that it will return a Fence once the Fence abstraction from PR llvm#1007 is available. - Metal: commit + waitUntilCompleted + error check - Vulkan: vkEndCommandBuffer + temporary fence + vkQueueSubmit + wait - DX12: CmdList Close + ExecuteCommandLists + fence signal/wait VulkanQueue now stores a VkDevice handle (with a TODO for lifetime management) so it can create/destroy fences independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move command buffer submission logic from each backend's Device into Queue::submit(), which takes ownership of the command buffers. For now it blocks internally until completion; a TODO marks that it will return a Fence once the Fence abstraction from PR llvm#1007 is available. - Metal: commit + waitUntilCompleted + error check - Vulkan: vkEndCommandBuffer + temporary fence + vkQueueSubmit + wait - DX12: CmdList Close + ExecuteCommandLists + fence signal/wait VulkanQueue now stores a VkDevice handle (with a TODO for lifetime management) so it can create/destroy fences independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move command buffer submission logic from each backend's Device into Queue::submit(), which takes ownership of the command buffers. For now it blocks internally until completion; a TODO marks that it will return a Fence once the Fence abstraction from PR llvm#1007 is available. - Metal: commit() + waitUntilCompleted() - Vulkan: vkEndCommandBuffer() + vkQueueSubmit() with temporary fence + vkWaitForFences() - DX12: CmdList::Close() + ExecuteCommandLists() + Queue::Signal()/Fence::SetEventOnCompletion() wait VulkanQueue now stores a VkDevice handle (with a TODO for lifetime management) so it can create/destroy fences independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move command buffer submission logic from each backend's Device into Queue::submit(), which takes ownership of the command buffers. For now it blocks internally until completion; a TODO marks that it will return a Fence once the Fence abstraction from PR llvm#1007 is available. - Metal: commit() + waitUntilCompleted() - Vulkan: vkEndCommandBuffer() + vkQueueSubmit() with temporary fence + vkWaitForFences() - DX12: CmdList::Close() + ExecuteCommandLists() + Queue::Signal()/Fence::SetEventOnCompletion() wait VulkanQueue now stores a VkDevice handle (with a TODO for lifetime management) so it can create/destroy fences independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
bogner
left a comment
There was a problem hiding this comment.
Generally looks reasonable, other than my usual gripe about shared_ptr
Move command buffer submission logic from each backend's Device into Queue::submit(), which takes ownership of the command buffers. For now it blocks internally until completion; a TODO marks that it will return a Fence once the Fence abstraction from PR llvm#1007 is available. - Metal: commit() + waitUntilCompleted() - Vulkan: vkEndCommandBuffer() + vkQueueSubmit() with temporary fence + vkWaitForFences() - DX12: CmdList::Close() + ExecuteCommandLists() + Queue::Signal()/Fence::SetEventOnCompletion() wait VulkanQueue now stores a VkDevice handle (with a TODO for lifetime management) so it can create/destroy fences independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
41f208f to
921ccb0
Compare
921ccb0 to
5605694
Compare
Move command buffer submission logic from each backend's Device into Queue::submit(), which takes ownership of the command buffers. For now it blocks internally until completion; a TODO marks that it will return a Fence once the Fence abstraction from PR llvm#1007 is available. - Metal: commit() + waitUntilCompleted() - Vulkan: vkEndCommandBuffer() + vkQueueSubmit() with temporary fence + vkWaitForFences() - DX12: CmdList::Close() + ExecuteCommandLists() + Queue::Signal()/Fence::SetEventOnCompletion() wait VulkanQueue now stores a VkDevice handle (with a TODO for lifetime management) so it can create/destroy fences independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use `= default` for Fence virtual destructor (consistent with Buffer)
- Fix member initializer order in DXFence and VulkanFence to match
declaration order (-Wreorder)
- Remove unnecessary braces in DXFence::waitForCompletion and
VulkanDevice::createFence
- Remove spurious blank line in DXFence::waitForCompletion
- Use consistent `device_or_resource_busy` errc for VK fence errors
instead of `not_enough_memory` / `invalid_argument /*todo*/`
- Drop unused VkResult from VulkanFence::getFenceValue
- Remove redundant zero-initialization of VkSemaphoreTypeCreateInfo
fields already handled by `= {}`
- Use unique_ptr for InvocationState::Fence in DX and MTL (consistent
with VK, fence is not shared)
- Drop unnecessary `this->` on createFence calls
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On WSL, HANDLE is void* but the event file descriptor is an int. Previously the code stored the fd in a HANDLE and used C-style casts to convert back. Use the proper ifdef pattern with the native type on each platform, matching the rest of the codebase. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Make constructors private and expose static create() methods on DXFence, VulkanFence, and MTLFence that return Expected<unique_ptr<...>>. This keeps all platform-specific resource creation encapsulated in each type, matching the pattern used for CommandBuffer. Each Device::createFence override now simply delegates to the respective Fence::create(). Also adds native accessors (getNativeFence, getNativeSemaphore, getNativeEvent) to replace direct member access. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
c4fda62 to
c8afeba
Compare
| HR::toError(IS.Fence->SetEventOnCompletion(CurrentCounter, Event), | ||
| "Failed to register end event.")) | ||
| return Err; | ||
| if (auto Err = IS.Fence->waitForCompletion(CurrentCounter)) |
There was a problem hiding this comment.
Can you explain why IS.Fence is being used here, instead of F?
There was a problem hiding this comment.
Because the goal is to use the new abstract interface. The only reason the fence is being downcast in the first place is because we don't have an abstract function for submitting on the queue yet.
lib/API/VK/Device.cpp
Outdated
| uint64_t Value = 0; | ||
| [[maybe_unused]] const VkResult Ret = | ||
| vkGetSemaphoreCounterValue(Device, Semaphore, &Value); | ||
| assert(!Ret); |
There was a problem hiding this comment.
I think it would be nice to && "error message" for this assert
|
LGTM |
Move command buffer submission logic from each backend's Device into Queue::submit(), which takes ownership of the command buffers. For now it blocks internally until completion; a TODO marks that it will return a Fence once the Fence abstraction from PR llvm#1007 is available. - Metal: commit() + waitUntilCompleted() - Vulkan: vkEndCommandBuffer() + vkQueueSubmit() with temporary fence + vkWaitForFences() - DX12: CmdList::Close() + ExecuteCommandLists() + Queue::Signal()/Fence::SetEventOnCompletion() wait VulkanQueue now stores a VkDevice handle (with a TODO for lifetime management) so it can create/destroy fences independently. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Fence type is modeled around the DX12 Fence. The closest equivalent in Vulkan is a Timeline Semaphore. These are widely supported and included in Vulkan 1.2. Metal provides the same functionality through the SharedEvent type.
The abstract interface allows us to wait for GPU work to complete by waiting on a signal value using:
Fence->waitForCompletion(SignalValue);Signaling the value still requires downcasting because queue submission is not done via an abstract interface yet.